using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace COMP376A3_9609695
{
    enum GameState { StartScreen, Play, Pause, Lose, Win };

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    /// 
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        private Model ballModel;
        private Model trackModel;
        private Model pillarModel;
        private Model blockModel;
        private Model billboardModel;

        private Texture2D sparkTex;

        private SpriteFont font;
        private SpriteFont loseFont;

        private const int ScreenWidth = 800;
        private const int ScreenHeight = 600;

        private Matrix cameraWorld = Matrix.CreateTranslation(0, -2f, 2f);
        private Vector3 up = new Vector3(0, 1, 1);
        private Matrix view;
        private Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(60), ((float)ScreenWidth / (float)ScreenHeight), 0.1f, 100f);

        private Matrix pillarWorld;// * Matrix.CreateScale(1.0f, 1.0f, 1.0f);

        private Ball ball;
        private Track track;
        private GameController control;
        private int loseTimer = 0;
        private int totalTime = 0;
        private bool spacePress;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = ScreenWidth;
            graphics.PreferredBackBufferHeight = ScreenHeight;
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            up = Vector3.UnitY;
            up.Normalize();
            view = Matrix.CreateLookAt(new Vector3(0, 0, -7), new Vector3(0, 0, 0), up);

            ballModel = Content.Load<Model>("Models/sphere");
            trackModel = Content.Load<Model>("Models/rampPart");
            pillarModel = Content.Load<Model>("Models/pillar");
            blockModel = Content.Load<Model>("Models/block");
            billboardModel = Content.Load<Model>("Models/billboard");

            sparkTex = Content.Load<Texture2D>("spark");

            font = Content.Load<SpriteFont>("scoreFont");
            loseFont = Content.Load<SpriteFont>("Lose");

            ball = new Ball(ballModel);
            track = new Track(trackModel);
            ball.Y = -1;
            cameraWorld = Matrix.Identity;
            cameraWorld *= Matrix.CreateTranslation(new Vector3(-ball.X, -ball.Y - 5, -ball.Z + 10f));
            cameraWorld *= Matrix.CreateRotationX(-(MathHelper.Pi / (32f * ball.maxmZ / 400 - 4)));

            control = new GameController(ball, blockModel);

            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            int elapsed = gameTime.ElapsedGameTime.Milliseconds;
            totalTime += elapsed;
            // Allows the game to exit
            KeyboardState keyState = Keyboard.GetState();
            if (keyState.IsKeyDown(Keys.Escape))
                this.Exit();
            switch (control.state)
            {
                case GameState.StartScreen:
                    if (keyState.IsKeyDown(Keys.Space) && !spacePress)
                    {
                        control.state = GameState.Play;
                        spacePress = true;
                    }
                    break;
                case GameState.Play:
                    if (keyState.IsKeyDown(Keys.Left))
                    {
                        ball.move(2f, 0, 0);
                    }

                    if (keyState.IsKeyDown(Keys.Right))
                    {
                        ball.move(-2f, 0, 0);
                    }

                    ball.move(0, 0, -2f);

                    if (keyState.IsKeyDown(Keys.Space) && !spacePress)
                    {
                        ball.jump();
                        spacePress = true;
                    }
                    if(keyState.IsKeyUp(Keys.Space) && spacePress)
                        spacePress = false;

                    Vector3 camPos = new Vector3(-ball.X, -ball.Y - 5, -ball.Z + 10f);
                    //float camRot = -(MathHelper.Pi / (32f * ball.maxmZ / 400 - 4) + (8 * ((ball.Y + 1) / 100) * MathHelper.Pi / 8));
                    float camRot = -(MathHelper.Pi / (32f * ball.maxmZ / 400 - 4));
                    float camRotY = 0;
                    if (keyState.IsKeyDown(Keys.T))
                    {
                        camPos = new Vector3(-ball.X, -ball.Y - 10, -ball.Z);
                        camRot = -MathHelper.Pi / 2;
                    }
                    if (keyState.IsKeyDown(Keys.L))
                    {
                        camPos = new Vector3(-ball.X + 10, -ball.Y - 1, -ball.Z);
                        camRot = 0;
                        camRotY = MathHelper.Pi / 2;
                    }

                    if (keyState.IsKeyDown(Keys.B))
                    {
                        camPos = new Vector3(-ball.X, -ball.Y - 2, -ball.Z + 10);
                        camRot = 0;
                    }


                    cameraWorld = Matrix.Identity;
                    cameraWorld *= Matrix.CreateTranslation(camPos);
                    cameraWorld *= Matrix.CreateRotationX(camRot);
                    cameraWorld *= Matrix.CreateRotationY(-camRotY);

                    ball.Update(gameTime);
                    track.Update(gameTime);
                    control.Update(gameTime);

                    if (ball.Y <= -100)
                    {
                        if (ball.Z >= control.maxDist && !control.infinite)
                            control.state = GameState.Win;
                        else
                            control.state = GameState.Lose;
                    }
                    break;
                case GameState.Lose:
                    loseTimer += elapsed;
                    if (loseTimer >= 2000)
                    {
                        loseTimer = 0;
                        ball = new Ball(ballModel);
                        control = new GameController(ball, blockModel); 
                        cameraWorld = Matrix.Identity;
                        cameraWorld *= Matrix.CreateTranslation(new Vector3(-ball.X, -ball.Y - 5, -ball.Z + 10f));
                        cameraWorld *= Matrix.CreateRotationX(-(MathHelper.Pi / (32f * ball.maxmZ / 400 - 4)));
                    }
                    break;
                case GameState.Win:
                    loseTimer += elapsed;
                    if (loseTimer >= 2000)
                    {
                        loseTimer = 0;
                        ball = new Ball(ballModel);
                        control = new GameController(ball, blockModel);
                        cameraWorld = Matrix.Identity;
                        cameraWorld *= Matrix.CreateTranslation(new Vector3(-ball.X, -ball.Y - 5, -ball.Z + 10f));
                        cameraWorld *= Matrix.CreateRotationX(-(MathHelper.Pi / (32f * ball.maxmZ / 400 - 4)));
                    }
                    break;
            }
            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
            float minZ = (float)Math.Floor(ball.Z / 10) * 10.0f - 10;
            Random rand = new Random();
            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone);

            DrawModel(ball.getModel(), ball.getModelMatrix(), view, projection);
            if (ball.sparks != null)
            {
                List<Matrix> sparks = ball.sparks.getDotMats();
                foreach (Matrix sparkMat in sparks)
                {
                    Vector3 randColor = fromHSV(rand.Next(60), (float)rand.NextDouble() / 2 + 0.5f, 1);
                    DrawModel(billboardModel, Matrix.CreateScale(0.5f)*sparkMat, view, projection, true, randColor.X, randColor.Y, randColor.Z);
                }
            }

            for (int r = 0; r != 20; r++)
            {
                if (control.infinite || minZ + r * 10 <= control.maxDist)
                {
                    Matrix trackPartWorld = track.getModelMatrix() * Matrix.CreateTranslation(0f, 0f, (float)r * 10 + minZ);
                    DrawModel(track.getModel(), trackPartWorld, view, projection);
                }
            }


            pillarWorld = Matrix.CreateTranslation(-1 * pillarModel.Meshes.ElementAt(0).BoundingSphere.Center);
            pillarWorld *= Matrix.CreateScale(1.0f, -1.0f, 1.0f);
            pillarWorld *= Matrix.CreateRotationX(-MathHelper.ToRadians(60 * ball.maxmZ / 400 + 30));
            pillarWorld *= Matrix.CreateTranslation(0, 0, 5);

            for (int pz = 0; pz != 20; pz++)
            {
                if (control.infinite || minZ + pz * 10 < control.maxDist)
                {
                    Matrix pillarPartWorld = pillarWorld * Matrix.CreateTranslation(8f, 0f, (float)pz * 10 + minZ);
                    DrawModel(pillarModel, pillarPartWorld, view, projection);
                    pillarPartWorld = pillarWorld * Matrix.CreateTranslation(-8f, 0f, (float)pz * 10 + minZ);
                    DrawModel(pillarModel, pillarPartWorld, view, projection);
                }
            }

            List<Block> blocks = control.blocks;
            foreach (Block block in blocks)
            {
                Vector3 newColor;
                if (block.sparksL != null)
                    newColor = new Vector3(0.4f, 0.4f, 0.4f);
                else
                    newColor = new Vector3(0.9f, 0.4f, 0.4f);
                if (block.colliding)
                {
                    newColor = new Vector3(0.8f, 0.4f, 0.0f);
                    //control.state = GameState.Lose;
                }
                    DrawModel(block.getModel(), block.getModelMatrix(), view, projection, true, newColor.X, newColor.Y, newColor.Z);
                    if (block.sparked)
                    {
                        List<Matrix> bsparks = block.sparksL.getDotMats();
                        foreach (Matrix sparkMat in bsparks)
                        {
                            Vector3 randColor = fromHSV(rand.Next(60), (float)rand.NextDouble() / 2 + 0.5f, 1);
                            DrawModel(billboardModel, sparkMat, view, projection, true, randColor.X, randColor.Y, randColor.Z);
                        }
                        bsparks = block.sparksR.getDotMats();
                        foreach (Matrix sparkMat in bsparks)
                        {
                            Vector3 randColor = fromHSV(rand.Next(60), (float)rand.NextDouble() / 2 + 0.5f, 1);
                            DrawModel(billboardModel, sparkMat, view, projection, true, randColor.X, randColor.Y, randColor.Z);
                        }
                    }
            }

            Vector3 screenSpace = graphics.GraphicsDevice.Viewport.Project(Vector3.Zero, projection, view, Matrix.CreateTranslation(ball.X, ball.Y + 3, ball.Z) * cameraWorld);

            string score = "Distance: ";
            if (control.infinite)
                score += (int)ball.Z;
            else if (ball.Z < control.maxDist)
                score += control.maxDist - (int)ball.Z;
            else score += 0;
            Vector2 textPosition = new Vector2(screenSpace.X - (font.MeasureString(score).X/2), screenSpace.Y);
            
            if (control.state == GameState.Lose)
            {
                string txt = "GAME OVER";
                Vector2 txtCenter = loseFont.MeasureString(txt);
                spriteBatch.DrawString(loseFont, txt, new Vector2(ScreenWidth / 2 - txtCenter.X / 2, textPosition.Y - txtCenter.Y/2 - font.MeasureString(score).Y), Color.White);
            } 
            if (control.state == GameState.Win)
            {
                string txt = "VICTORY!";
                Vector2 txtCenter = loseFont.MeasureString(txt);
                spriteBatch.DrawString(loseFont, txt, new Vector2(ScreenWidth / 2 - txtCenter.X / 2, textPosition.Y - txtCenter.Y / 2 - font.MeasureString(score).Y), Color.White);
            }
            if (control.state == GameState.StartScreen)
            {
                string txt = "Press Space to start";
                Vector2 txtCenter = loseFont.MeasureString(txt);
                spriteBatch.DrawString(loseFont, txt, new Vector2(ScreenWidth / 2 - txtCenter.X / 2, ScreenHeight * 3 / 4 - txtCenter.Y / 2), Color.White);
            } 
            else 
                spriteBatch.DrawString(font, score, textPosition, Color.White);
            spriteBatch.End();

            base.Draw(gameTime);
        }

        private void DrawModel(Model model, Matrix world, Matrix view, Matrix projection, bool recolor = false, float newR=0, float newG=0, float newB=0)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    if (recolor)
                    effect.DiffuseColor = new Vector3(newR, newG, newB);
                    effect.EnableDefaultLighting();
                    effect.DirectionalLight0.DiffuseColor = new Vector3(0.2f, 0.2f, 0.2f);
                    effect.DirectionalLight0.Direction = new Vector3(0.5f, -0.5f, 0);;
                    effect.AmbientLightColor = new Vector3(0.5f, 0.5f, 0.5f);
                    //effect.EmissiveColor = new Vector3(0, 0, 0);
                    effect.SpecularColor = new Vector3(1.0f, 1.0f, 1.0f);
                    effect.SpecularPower = 100f;
                    effect.LightingEnabled = true;
                    effect.World = world * cameraWorld;
                    effect.View = view;
                    effect.Projection = projection;

                    effect.FogEnabled = true;
                    effect.FogColor = Color.Black.ToVector3();
                    effect.FogStart = 50.0f;
                    effect.FogEnd = 100.0f;
                }

                mesh.Draw();
            }
        }

        private Vector3 fromHSV(float H, float S, float V)
        {
            Vector3 rgb = new Vector3();
            if (V < 0 || V > 1)
                V /= V;
            if (S < 0 || S > 1)
                S /= S;
            H = H % 360;
            float chroma = V * S;
            float hPrime = H / 60;
            float X = chroma * (1 - (float)Math.Abs(hPrime % 2 - 1));

            if (0 <= hPrime && hPrime < 1)
            {
                rgb.X = chroma;
                rgb.Y = X;
                rgb.Z = 0;
            }
            if (1 <= hPrime && hPrime < 2)
            {
                rgb.X = X;
                rgb.Y = chroma;
                rgb.Z = 0;
            }
            if (2<= hPrime && hPrime < 3)
            {
                rgb.X = 0;
                rgb.Y = chroma;
                rgb.Z = X;
            }
            if (3 <= hPrime && hPrime < 4)
            {
                rgb.X = 0;
                rgb.Y = X;
                rgb.Z = chroma;
            }
            if (4 <= hPrime && hPrime < 5)
            {
                rgb.X = X;
                rgb.Y = 0;
                rgb.Z = chroma;
            }
            if (5 <= hPrime && hPrime < 6)
            {
                rgb.X = chroma;
                rgb.Y = 0;
                rgb.Z = X;
            }
            float m = V - chroma;
            rgb += new Vector3(m, m, m);

            return rgb;
        }
    }
}
